﻿namespace JoinBox.WPF.LabelBarStandard.View;

using JoinBox.WPF.LabelBarStandard.ViewModel;

using System.Windows;
using MenuItem = System.Windows.Controls.MenuItem;
using UserControl = System.Windows.Controls.UserControl;
using RadioButton = System.Windows.Controls.RadioButton;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using System.Windows.Threading;
using System.Diagnostics;
using Button = System.Windows.Controls.Button;
using System.Windows.Media.Animation;

// 此类将加载到一个同步上下文中
public partial class DocWindow : UserControl
{
    public RelayCommand<DocElement> CreateCmd { set; get; }
    public RelayCommand<DocElement> CloseCmd { set; get; }
    public RelayCommand<DocElement> TabCmd { set; get; }

    // 与界面绑定的会通过这个类
    public DocumentTabData TabData;

    /// <summary>
    /// 另存为之后立即关闭文档
    /// </summary>
    Document? _IsCloseDocument = null;

    /// <summary>
    /// 文档标签的WPF
    /// </summary>
    /// <param name="acapIntermediary">必须传参进来,而且要在同步线程</param>
    public DocWindow()
    {
        // 点击按钮就会触发命令
        TabData = new DocumentTabData();
        TabCmd = new RelayCommand<DocElement>((info) => {
            // 选中按钮
            TabData.DocElementPick = info;
#if CAD
            AutoGo.Post(() => {
                var doc = info.Document;
                if (doc != null && !doc.IsActive && !doc.IsDisposed)
                    Acap.DocumentManager.MdiActiveDocument = doc;
            });
#endif
        });

        CloseCmd = new RelayCommand<DocElement>(Post_CloseDwg);
        CreateCmd = new RelayCommand<DocElement>((info) => {
#if CAD
            // 重置cad之后这两项的系统变量会弹窗,所以设置一下
            AutoGo.Post(() => {
                var docm = Acap.DocumentManager;
                var doc = docm?.MdiActiveDocument;
                if (doc != null)
                {
                    var year = Env.GetAcadVersion();
                    if (year >= 2014)
                        Env.SetVar("secureload", 0); // 2014加载lisp不警告
                    Env.SetVar("acadlspasdoc", 1);   // 将acad.lsp文件加载到每一个图形
                }
            });
            Post_Qnew();
#endif
        });

        InitializeComponent();// 这里弹错表示去项目文件取消迭代版本号
        DataContext = TabData;
        Loaded += DocWindow_Loaded;
    }

    private void DocWindow_Loaded(object sender, RoutedEventArgs e)
    {
#if !CAD
        for (int i = 0; i < 6; i++)
        {
            var a = new DocElement("文档名示意" + i)
            {
                Document = new Document(),
            };
            TabData.DocElements.Add(a);
        }
#endif

#if CAD
        AutoLoadRun();
        Acap.DocumentManager.DocumentCreated += AutoLoadRun;

        // 反应器->销毁前返回文档(先)
        Acap.DocumentManager.DocumentToBeDestroyed += DocumentManager_DocumentToBeDestroyed;
        // 反应器->销毁前返回文档名(后)
        Acap.DocumentManager.DocumentDestroyed += DocumentManager_DocumentDestroyed;

        // 反应器->否决命令
        Acap.DocumentManager.DocumentLockModeChanged += DocumentManager_DocumentLockModeChanged;
        // 反应器->切换文档就事件激活
        Acap.DocumentManager.DocumentActivated += DocumentManager_DocumentActivated;

        // 由于挤出的空间挡住了非最大化cad文档的空间,所以需要设置一下非最大化时候的文档.
        AutoGo.Post(() => {
            AnchorHelper.ReWindowSize();
        });
#endif
    }

    /// <summary>
    /// 关闭当前dwg
    /// </summary>
    /// <param name="doc"></param>
    private void Post_CloseDwg(DocElement info)
    {
        // 福萝卜用的是cad-com接口的saved可以判断是否已经保存
        string file = string.Empty;

        var doc = info.Document;
        if (doc == null)
            return;

#if CAD
        AutoGo.Post(() => {
            ReplaceActiveDocument(doc);

            // 如果数据库没有更改过,那么就直接关闭掉吧~
            // 仅缩放时候:数据库没有改变,但是变量变了,视图变了,就关掉好了
            var dbmod = DBmodEx.DBmod;
            if (dbmod == DBmod.DatabaseNoModifies
                ||
                (((dbmod & DBmod.Database) != DBmod.Database)
                 && ((dbmod & DBmod.Value) == DBmod.Value)
                 && ((dbmod & DBmod.View) == DBmod.View)))
            {
                doc.CloseAndDiscard();
                return;
            }

            file = doc.Database.Filename;
            // 自动保存时候产生的扩展名
            // if (Path.GetExtension(file) == ".sv$")
            //    file = doc.Database.OriginalFileName;

            // 如果是dwt就需要提示成dwg,然后另存为
            // 如果是dwg就可以直接保存
            var originEx = Path.GetExtension(file).ToLower();
            file = Path.Combine(Path.GetDirectoryName(doc.Database.OriginalFileName), doc.Name);

            // 数据库更改过但是没有保存过,就询问用户是否保存
            var save = MessageBox.Show($"您已经修改过图纸,保存文档?\n\n{file}",
                                         "惊惊盒子",
                                         MessageBoxButton.YesNoCancel);
            if (save == MessageBoxResult.Cancel)
                return;
            if (save == MessageBoxResult.No)
            {
                AutoGo.Post(() => {
                    doc.CloseAndDiscard(); /*直接关闭不保存*/
                });
                return;
            }

            if (originEx == ".dwt")
            {
                // 用Qsave的话,按了保存,然后右上角的X此时就关闭了对话框和文档,造成丢失
                // 而它就只会关闭了对话框,不会关闭文档
                Post_SaveAs(info);
            }
            else if (originEx == ".dwg" || originEx == ".dxf")
            {
                if (!doc.IsReadOnly)// 只读文件
                    doc.CloseAndSave(file);
                else
                    Post_SaveAs(info);
            }
            else
            {
                _IsCloseDocument = info.Document;
                Post_Qsave(info);
            }
        });
#endif
    }

#if CAD
    /// <summary>
    /// 切换到当前文档
    /// </summary>
    /// <param name="doc"></param>
    private static void ReplaceActiveDocument(Document doc)
    {
        // 切换到当前文档
        // 如果不是当前文档,就切换过去,否则拿不到修改的变量
        if (doc != null && doc != Acap.DocumentManager.MdiActiveDocument)
            Acap.DocumentManager.MdiActiveDocument = doc;
    }

    /// <summary>
    /// 获取dwg名称
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public static string GetDocName(Document doc)
    {
        var file = doc.Database.Filename;
        var ex = Path.GetExtension(file).ToLower();
        if (!(ex == ".dwg" || ex == ".dxf"))
            file = doc.Name;
        return Path.GetFileNameWithoutExtension(file);
    }

    /// <summary>
    /// 每次新建文档的自动执行
    /// </summary>
    public void AutoLoadRun(object? sender = null, DocumentCollectionEventArgs? e = null)
    {
        Invoke(() => {
            // cad08这个事件可能会重复两次,所以只能清空再加入
            TabData.DocElements.Clear();

            // 重建表格(防止tab占用Doc,所以加入名称)
            foreach (Document doc in Acap.DocumentManager)
            {
                if (doc == null || doc.IsDisposed)// doc.IsActive初始化时候异常
                    continue;
                var info = new DocElement(GetDocName(doc))
                {
                    Document = doc,
                };
                TabData.DocElements.Add(info);
            }
        });

        // 第一个文档不会调用激活事件,所以这里要写Pick标签
        if (e == null)
            PickDocument();
    }

    /// <summary>
    /// 选中当前活动文档的焦点
    /// </summary>
    /// <returns></returns>
    private Document? PickDocument(Document? doca = null)
    {
        Document? pick = null;
        if (doca != null)
        {
            pick = doca;
        }
        else
        {
            // 自运行一次时候,设置为激活的文档.
            foreach (Document doc in Acap.DocumentManager)
            {
                if (doc == null || doc.IsDisposed)// doc.IsActive初始化时候异常
                    continue;
                pick = doc;
                break;
            }
        }

        Invoke(() => {
            if (pick != null)
                TabData.InputPick = pick;// 新建了就激活为当前按钮
        });
        return pick;
    }

    /// <summary>
    /// 反应器->切换文档就事件激活
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void DocumentManager_DocumentActivated(object? sender = null, DocumentCollectionEventArgs? e = null)
    {
        // 切换文档标签激活
        if (e != null)
            PickDocument(e.Document);
    }

    /// <summary>
    /// 反应器->销毁前返回文档(先)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void DocumentManager_DocumentToBeDestroyed(object? sender, DocumentCollectionEventArgs? e)
    {
        if (e == null)
            return;

        // 操作WPF(界面)是要线程安全,操作cad不用
        Invoke(() => {
            for (int i = TabData.DocElements.Count - 1; i >= 0; i--)
                if (TabData.DocElements[i].Document == e.Document)
                {
                    TabData.DocElements.RemoveAt(i);
                    break;
                }
        });
    }

    /// <summary>
    /// 反应器->销毁前返回文档名(后)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void DocumentManager_DocumentDestroyed(object? sender, DocumentDestroyedEventArgs? e)
    {
        if (e == null)
            return;

        // 反应器->切换文档就事件激活也会设置焦点,所以此事件废弃.
        // 重设焦点
        // PickDocument();
    }

    HashSet<string> _CmdNewsDwgFile = new() { "NEW", "QNEW", "_NEW", "_QNEW" };

    /// <summary>
    /// 反应器->命令否决触发命令前(不可锁文档)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void DocumentManager_DocumentLockModeChanged(object? sender, DocumentLockModeChangedEventArgs? e)
    {
        if (e == null)
            return;

        var up = e.GlobalCommandName.ToUpper();

        if (_CmdNewsDwgFile.Contains(up))
        {
            e.Veto(); // 否决命令

            // 如果直接调用了DocWindow.QnewMethod()可能会令否决失败,
            // 因为用户可能和我一样有其他插件,
            // 而其他插件被加载时候会产生清空反应器的作用(反应器有计数超出不执行的情况).
            // 所以要以发送异步命令到cad
            // SendStringToExecute第二个参数true时,命令必须有CommandFlags.Session,否则容易致命错误
            AutoGo.Post(() => {
                e.Document?.SendStringToExecute(
                    nameof(CadCommand.QnewMethod) + "\n", true, false, false);
            });
        }
        if (up == "#QSAVE" && _IsCloseDocument != null)
        {
            AutoGo.Post(() => {
                _IsCloseDocument.CloseAndDiscard();
                _IsCloseDocument = null;
            });
        }

        if (up == "#QSAVE" || up == "#SAVEAS")
        {
            // 执行了另存为之后,只能这里再刷新文档栏
            // 因为这两个命令都有可能变成另存为,
            // 而另存为时候用户可能修改文档名,
            // 所以这里实现更改文档栏的名称
            foreach (var infoa in TabData.DocElements)
            {
                if (infoa.Document == null)
                    continue;
                infoa.InfoDocName = GetDocName(infoa.Document);
                infoa.Document = infoa.Document;//触发关联
            }
        }

        // ctrl+q触发 _quit关闭时候
        // 0x777CB922 (KernelBase.dll)处(位于 acad.exe 中)引发的异常: 0x000006C6: 数组绑定无效。。
        if (up == "QUIT")
        {
            e.Veto(); // 否决命令
            var info = TabData.DocElements.FirstOrDefault(item => item.Document == e.Document);
            if (info != null)
                Post_CloseDwg(info);
        }
    }

    /// <summary>
    /// 创建文档_自己写的版本_不可以改用发送消息版本,否则致命错误概率提高
    /// </summary>
    public static void Post_Qnew()
    {
        AutoGo.Printl("Post_Qnew");
        AutoGo.Post(CadCommand.QnewMethod);
    }

    /// <summary>
    /// 打开_dwg
    /// </summary>
    public static void Post_OpenDwg()
    {
        // 因为当前文档必然存在,所以直接发送命令就好了
        AutoGo.Post(() => {
            var doc = Acap.DocumentManager.MdiActiveDocument;
            doc.SendStringToExecute("_Open\n", false, true, true); // 不需要切换文档
        });
    }

    /// <summary>
    ///  保存_dwg
    /// </summary>
    public static void Post_Qsave(DocElement info)
    {
        // 因为当前文档必然存在,所以直接发送命令就好了
        AutoGo.Post(() => {
            info.Document?.SendStringToExecute("_Qsave\n", false, true, true); // 不需要切换文档
        });
    }

    /// <summary>
    /// 另存为_dwg
    /// </summary>
    /// <param name="info"></param>
    public static void Post_SaveAs(DocElement info)
    {
        // 如果是只读状态,发送qsave是会提示只读,不会调用另存的,所以必须要一个另存为命令
        AutoGo.Post(() => {
            info.Document?.SendStringToExecute("_Saveas\n", false, true, true); // 不需要切换文档
        });
    }
#endif

    /// <summary>
    /// 控制线程是否作为WPF线程<br/>
    /// net35为true,net35+为false
    /// net35 必须要跑新线程上面<br/>
    /// net35+ 也能用,但是会卡顿,卡顿发生在最大化和还原窗口/拖动窗口边界<br/>
    /// 所以 net35+ 新建线程之后,通过post在主线程上面嵌入了<br/>
    /// 原因是微软在 net35+ 上面修复了WPF的线程模型,<br/>
    /// 所以可以直接利用背景线程进行嵌入<br/>
    /// </summary>

#if NET35
    public static bool DispatcherRunFlag => true;
#else
    public static bool DispatcherRunFlag => false;
#endif

    /// <summary>
    /// WPF界面线程安全
    /// </summary>
    /// <param name="ac"></param>
    public void Invoke(Action ac)
    {
        if (DispatcherRunFlag)
        {
            // acad08利用新线程,所以是 Running
            if (Dispatcher.Thread.ThreadState == System.Threading.ThreadState.Running)
                Dispatcher.Invoke(ac);
            else
                Debug.WriteLine("DocWindow.ThreadState::" + Dispatcher.Thread.ThreadState);
        }
        else
        {
            // 高版本利用cad主窗口线程,所以说 Background
            if (Dispatcher.Thread.ThreadState == System.Threading.ThreadState.Background)
                Dispatcher.Invoke(ac);
            else
                Debug.WriteLine("DocWindow.ThreadState::" + Dispatcher.Thread.ThreadState);
        }
    }

    /// <summary>
    /// 右键菜单
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void MenuItem_Click(object sender, RoutedEventArgs e)
    {
#if CAD
        if (!(sender is MenuItem me && me.DataContext is DocElement info))
            return;

        switch (me.Header)
        {
            case "新建dwg":
                Post_Qnew();
                break;
            case "打开dwg":
                Post_OpenDwg();
                break;
            case "保存":
                Post_Qsave(info);// 这是异步的,去拦截命令上面刷新一次
                break;
            case "另存":
                Post_SaveAs(info);// 这是异步的,去拦截命令上面刷新一次
                break;
            case "全部保存":
                foreach (var infoa in TabData.DocElements)
                    Post_Qsave(infoa);
                break;
            /**********************/
            case "关闭":
                Post_CloseDwg(info);
                break;
            case "关闭全部(会提示保存)":
                foreach (var infoa in TabData.DocElements)
                    Post_CloseDwg(infoa);
                break;
            case "关闭全部(不保存)":
                AutoGo.Post(Acap.DocumentManager.CloseAll);
                break;
            case "关闭所有其他图纸(保存)":
                foreach (var item in this.TabData.DocElements)
                    if (item.Document != info.Document)
                        Post_CloseDwg(item);
                break;
            case "关闭所有其他图纸(不保存)":
                AutoGo.Post(() => {
                    foreach (Document doc in Acap.DocumentManager)
                        if (info.Document != doc)
                            doc.CloseAndDiscard();
                });
                break;
            /**********************/
            case "复制完整文件夹路径":
                if (info.Document == null)
                    break;
                Clipboard.SetText(info.Document.Database.Filename);
                break;
            case "打开文件的位置":
                {
                    if (info.Document == null)
                        break;

                    // 不重复打开dwg路径的资源管理器(高版本把没有保存过的设置为不可用按钮)
                    var db = info.Document.Database;
                    var ex = Path.GetExtension(db.Filename).ToLower();

                    // 这里需要判断是否为只读
                    if (ex == ".dwg" || ex == ".dxf")
                    {
                        var wins = new ShellWindows();// 这个名字是为了兼容高版本(没想到吧
                        if (wins.Count > 0)
                        {
                            var dwgname = Env.GetVar("dwgname").ToString();
                            foreach (var item in wins)
                                if (item.LocationURL != null &&
                                    item.LocationURL + "\\" + dwgname == db.Filename)
                                    item.Quit();// 关闭
                        }
                        // 重开一个,防止选择状态被改变,
                        Process.Start("explorer", "/select,\"" + db.Filename + "\"");// 加引号防止空格中断
                    }
                    else
                    {
                        AutoGo.Post(() => {
                            var ed = info.Document.Editor;
                            ed.WriteMessage(Environment.NewLine + "你没有保存文件!\n");
                        });
                    }
                }
                break;
        }
#endif
    }

    /// <summary>
    /// 鼠标按下事件
    /// 不要用MouseDown因为绑定了command之后就无法判断左键了,只能用PreviewMouseDown
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void RadioButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        // 中键关闭文档
        if (e.MiddleButton == MouseButtonState.Pressed &&
            e.Source is RadioButton ra2 &&
            ra2.CommandParameter is DocElement info2)
            Post_CloseDwg(info2);
    }

    Point mousePointBak = new();
    bool canvas2_isMove = false;

    /// <summary>
    /// 鼠标左键按下(比命令绑定先运行)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void RadioButton_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // if (e.LeftButton == MouseButtonState.Pressed &&
        //    e.Source is RadioButton ra1 &&
        //    ra1.CommandParameter is Info info1)
        // {
        // }

        // 获取鼠标
        canvas2_isMove = true;
        mousePointBak = e.GetPosition(null);
    }

#if false
    /// <summary>
    /// 鼠标移动
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void RadioButton_MouseMove(object sender, MouseEventArgs e)
    {
        Point p = Mouse.GetPosition(e.Source as FrameworkElement);
        AutoGo.Print(sender);

        if (e.LeftButton != MouseButtonState.Pressed)// 按着左键
            return;
        if (_JRadioButtonData == null)
            return;
        if (e.Source is RadioButton ra1 && ra1 != _JRadioButtonData.RadioButton)// 不相等就是交换,但是按着左键的的时候不切换e.Source
        {
            // 获取鼠标位置
            var X = e.GetPosition(ra1).X;
            var Y = e.GetPosition(ra1).Y;
            var pt = new Point(X, Y);

            // 偏移量
            var offset = Math.Abs(_JRadioButtonData.Pt.X - pt.X);
            if (offset != 0)
            {
                var DocElements2 = this.TabData.DocElements.ToList();
                // 记录选中文档按钮的位置,然后清理所有按钮
                int number = 0;
                for (int i = 0; i < this.TabData.DocElements.Count - 1; i++)
                {
                    if (this.TabData.DocElements[i] == _JRadioButtonData.Info)
                    {
                        number = i;
                        break;
                    }
                }
                this.TabData.DocElements.Clear();
                if (offset > 0)  // 往→
                    ++number;
                else // 往←
                    --number;

                for (int i = 0; i < DocElements2.Count - 1; i++)
                {
                    if (i == number)
                        this.TabData.DocElements.Add(_JRadioButtonData.Info);
                    else
                        this.TabData.DocElements.Add(DocElements2[i]);
                }
            }
        }
    }
#endif

    // 窗体_鼠标抬起
    private void Window_MouseUp(object sender, MouseButtonEventArgs e)
    {
        canvas2_isMove = false;
        // canvas2.Background = Brushes.Yellow;
    }

    // 窗体_鼠标移动
    private void Window_MouseMove(object sender, MouseEventArgs e)
    {
        if (!canvas2_isMove)
            return;

        if (e.Source is not RadioButton canvas2)
            return;

        // canvas2.Background = Brushes.Red;

#if false
        var currEle = sender as FrameworkElement;// 如果是窗体,那么这里会移动窗体

        // 通过鼠标来控制_约束移动方向
        double xPos = e.GetPosition(null).X - oldPoint.X + (double)currEle.GetValue(Canvas.LeftProperty);
        currEle.SetValue(Canvas.LeftProperty, xPos);

        // double yPos = e.GetPosition(null).Y - oldPoint.Y + (double)currEle.GetValue(Canvas.TopProperty);
        // currEle.SetValue(Canvas.TopProperty, yPos);
#else
        // 通过鼠标来控制_约束移动方向
        double xPos = e.GetPosition(null).X - mousePointBak.X + (double)canvas2.GetValue(Canvas.LeftProperty);
        canvas2.SetValue(Canvas.LeftProperty, xPos);

        // double yPos = e.GetPosition(null).Y - oldPoint.Y + (double)canvas2.GetValue(Canvas.TopProperty);
        // canvas2.SetValue(Canvas.TopProperty, yPos);
#endif
        mousePointBak = e.GetPosition(null);
    }


    // https://www.cnblogs.com/luyingxue/articles/1280787.html
    private void Btn_Gear_Click(object sender, RoutedEventArgs e)
    {
        if (JJStoryboard.GetIsPaused(this))
            JJStoryboard.Resume(this);
        else
            JJStoryboard.Pause(this);
    }
}